let CIRCLE_SELECT = false; // 是否圈选模式
let source = undefined; // 来源页面
let activeElement = null; // 被选中的元素

// 设置圈选元素类名 样式是红色边框
const CLASS_NAME = 'pp-react-tract__active-element';
const style = document.createElement('style');
style.innerHTML = `.${CLASS_NAME} { border:1px solid red!important; } `;
document.head.appendChild(style);

/**
 * 用户监听 开启/关闭 圈选模式
 */
window.addEventListener('message', function (e) {
  const { isCircleSelect } = e.data;
  CIRCLE_SELECT = isCircleSelect;
  source = e.source;
});

/**
 * 节点类型说明
 */
const FunctionComponent = 0;
const ClassComponent = 1;

/**
 * 每次 react 运行，randomKey 只会生成一次，生成规则是  __reactInternalInstance$ + 随机串。也可以通过这个方法，从 dom 实例获取 fiberNode。 const getInstanceFromNode$1 = ReactDOM.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Events.find(fn => typeof fn === 'function' && fn.name === 'getInstanceFromNode$1');
 */
let randomKey = undefined;

/**
 * react fiberNode 与 DOM 实例对应的引用属性起始字符串
 */
const MATCH_STARTS_STRING = `__reactInternalInstance$`;

/**
 * 直接监听 document 的点击事件，在使用 react 的点击事件的时候，即使在函数内部阻止了冒泡，也会在 document 上监听到点击事件
 * 但是这里选择监听 react 挂载节点，在圈选时候需要阻止冒泡到 document，因为冒泡到 document 会触发 react 事件系统接着触发业务中绑定的事件，这不是圈选的时候期望触发的
 */
document.getElementById('container').addEventListener('click', (e) => {
  // 如果不存在就获取一次，之后就可以一直使用
  if (!randomKey) {
    const privateKeys = Object.keys(e.target);
    randomKey = privateKeys.find((privateKey) =>
      privateKey.startsWith(MATCH_STARTS_STRING)
    );
  }

  // 如果包含 randomKey 的元素，说明是 react 渲染出来的元素
  if (e.target[randomKey]) {
    // console.log('当前 fiberNode：', e.target[randomKey]); // 获取到的 DOM 实例对应的 fiberNode
    // console.log('当前 DOM 实例：', e.target); // 获取到的 DOM 实例

    const currentFiberNode = e.target[randomKey];
    let dataSnapshot = getDataSnapshot(currentFiberNode);
    let xPath = getXPath(e.target);

    if (CIRCLE_SELECT) {
      // 如果圈选模式，阻止冒泡到 document，不触发 react 事件系统
      e.stopPropagation();

      // 设置和取消圈选样式
      if (activeElement) {
        const classNameList = activeElement.getAttribute('class');
        activeElement.setAttribute(
          'class',
          classNameList.replace(CLASS_NAME, '')
        );
      }
      activeElement = e.target;
      const classNameList = activeElement.getAttribute('class');
      activeElement.setAttribute(
        'class',
        classNameList ? `${classNameList} ${CLASS_NAME}` : CLASS_NAME
      );

      // 发送数据给 iframe
      source.postMessage({
        type: 'pp-react-track',
        data: { dataSnapshot, xPath },
      });
    } else {
      if (
        // 如果是以 ] 结尾，说明是具体的 xPath，比如 id("container")/div[0]/div[0]/p[2]
        pathConfigFromServer.xPath.endsWith(']') &&
        pathConfigFromServer.xPath === xPath
      ) {
        // 首先匹配当前 xPath 是否和后台全选的 xPath 一致，一致说明是需要上报的
        pathConfigFromServer.dataPathList.forEach((dPath) => {
          console.log('上报数据', {
            xPath: pathConfigFromServer.xPath,
            data: getDataByDPath(dPath, dataSnapshot),
          });
        });
      } else if (pathConfigFromServer.xPath === xPath.replace(/\[\d+\]$/, '')) {
        // 说明的概略路径比如 id("container")/div[0]/div[0]/p 不包含 [x] 第几项
        pathConfigFromServer.dataPathList.forEach((dPath) => {
          console.log('上报数据', {
            xPath: pathConfigFromServer.xPath,
            data: getDataByDPath(dPath, dataSnapshot, currentFiberNode.key),
          });
        });
      }
    }
  }
});

/**
 * 获取数据快照，当前点击的 fiberNode 到根组件的数据
 * @param {*} fiberNode
 */
function getDataSnapshot(fiberNode) {
  let data = [];
  while (fiberNode) {
    let currentNodeData = {};
    if (fiberNode.tag === FunctionComponent) {
      // FIXME 方法组件是链表，要遍历获取完整数据，建议数据全部使用一个 useParams 比较好
      currentNodeData = {
        state: fiberNode.memoizedState.baseState,
        props: fiberNode.memoizedProps,
        componentName: fiberNode.type.name,
      };
      data.unshift(currentNodeData);
    } else if (fiberNode.tag === ClassComponent) {
      currentNodeData = {
        state: fiberNode.memoizedState,
        props: fiberNode.memoizedProps,
        componentName: fiberNode.type.name,
      };
      data.unshift(currentNodeData);
    }
    fiberNode = fiberNode.return;
  }

  data.reduce((result, next) => {
    result.child = next;
  });
  return data[0];
}

/**
 * 获取当前点击节点 xpath
 * @param {*} element
 */
function getXPath(element) {
  if (element.id !== '') return 'id("' + element.id + '")';
  if (element === document.body) return element.tagName;

  var ix = 0;
  var siblings = element.parentNode.childNodes;
  for (var i = 0; i < siblings.length; i++) {
    var sibling = siblings[i];
    if (sibling === element)
      return (
        getXPath(element.parentNode) +
        '/' +
        element.tagName.toLowerCase() +
        '[' +
        ix +
        ']'
      );
    if (sibling.nodeType === 1 && sibling.tagName === element.tagName) ix++;
  }
}

/**
 * 通过 dPath 获取数据
 * @param {*} dPath
 * @param {*} store
 * @param {*} id 这个是可选参数，用于路径里出现 通配符（*） 的时候使用
 */
function getDataByDPath(dPath, store, id) {
  try {
    let alternateStore = store;
    if (typeof dPath === 'string') {
      const dPathSplitList = dPath.split('/');
      while (dPathSplitList.length && alternateStore) {
        const key = dPathSplitList.shift();
        if (key === '*') {
          // 等于星号是通配符的意思，当前这一项是个数组
          alternateStore = alternateStore.find((item) => {
            return item.id == id; // 这里没用全等的原因是 fiberNode 上的 key 是字符串，我们从服务端取回的数据 id 可能是数值啥的
          });
        } else if (alternateStore[key]) {
          alternateStore = alternateStore[key];
        } else {
          break;
        }
      }
    }
    return `当前 dPath（${dPath}） 对应的值为：${alternateStore}`;
  } catch (e) {
    console.log(e);
  }
}
